#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter_ipv4.h>
#include <linux/list.h>
#include <net/ip.h>
#include "../include/pub.h"
#include "uf_rule.h"

struct list_head UF_rulelist;
rwlock_t urlrule_lock;
struct ts_config *match_get;
struct ts_config *match_post;
unsigned int default_act = NF_ACCEPT;
unsigned int url_advanced = 0;
unsigned int match_debugging = 0;
unsigned int match_count = 0;
unsigned int default_debugging = 0;
unsigned int default_count = 0;

void UF_build_get(void)
{
    const char *pattern_get = "GET ";
    const char *pattern_post = "POST ";
    
    match_get = textsearch_prepare("kmp", pattern_get, strlen(pattern_get), GFP_ATOMIC, TS_AUTOLOAD);
    match_post = textsearch_prepare("kmp", pattern_post, strlen(pattern_post), GFP_ATOMIC, TS_AUTOLOAD);
    if (IS_ERR(match_get) || IS_ERR(match_post))
    {
        //printk(KERN_EMERG"UF_build_conf ...\n"); 
    }
}

void UF_release_get(void)
{
    if ((NULL != match_get) && (!IS_ERR(match_get)))
    {
        textsearch_destroy(match_get);
        match_get = NULL;
    }
    if ((NULL != match_post) && (!IS_ERR(match_post)))
    {
        textsearch_destroy(match_post);
        match_post = NULL;
    }
}

void UF_build_conf(UF_kruleinfo_S *prule)
{
    const char *pattern = prule->rule.url;
    
    if ((NULL != prule->match_url) && (!IS_ERR(prule->match_url)))
    {
        textsearch_destroy(prule->match_url);
        prule->match_url = NULL;
    }
    
    prule->match_url = textsearch_prepare("kmp", pattern, strlen(pattern), GFP_ATOMIC, TS_AUTOLOAD);
    if (IS_ERR(prule->match_url))
    {
        //printk(KERN_EMERG"UF_build_conf ...\n"); 
    }

    return ;
}

int UF_proc_addurl(UF_RuleInfo_S *pruleinfo, UF_CfgOutput_S *presult)
{
    UF_kruleinfo_S *prule = NULL;
    UF_kruleinfo_S *prule_temp = NULL;
    struct list_head *pos, *temp;
    
    list_for_each_safe(pos, temp, &UF_rulelist) 
    {
        prule_temp = list_entry(pos, UF_kruleinfo_S, list);
        if (0 == strcmp(pruleinfo->url, prule_temp->rule.url))
        {
            prule = prule_temp;
            break;
        }
	}

    if (prule)
    {
        printk(KERN_EMERG"url %s exist.\n", pruleinfo->url);
        return 0;
    }    

    prule = (UF_kruleinfo_S *)kzalloc(sizeof(UF_kruleinfo_S), GFP_ATOMIC);
    if (NULL == prule)
    {
        printk(KERN_EMERG"add url=%s, non-memory.\n", pruleinfo->url);    
        return -1;
    }

    memcpy(&prule->rule, pruleinfo, sizeof(UF_kruleinfo_S));
    UF_build_conf(prule);
    list_add_tail(&prule->list, &UF_rulelist);
    printk(KERN_EMERG"add url=%s ok\n", pruleinfo->url);
    return 0;
};

int UF_proc_delurl(UF_RuleInfo_S *pruleinfo, UF_CfgOutput_S *presult)
{
    UF_kruleinfo_S *prule = NULL;
    struct list_head *pos, *temp;

    list_for_each_safe(pos, temp, &UF_rulelist) 
    {
        prule = list_entry(pos, UF_kruleinfo_S, list);
        if (0 == strcmp(pruleinfo->url, prule->rule.url))
        {
            if ((NULL != prule->match_url) && (!IS_ERR(prule->match_url)))
            {
                textsearch_destroy(prule->match_url);
                prule->match_url = NULL;
            }
            
            list_del(pos);
		    kfree(prule);
            break;
        }
	}
       
    return 0;
};

int UF_proc_show(UF_ShowHead_S *phead, UF_RuleInfo_S *prule, int max_getcnt)
{
    UF_kruleinfo_S *prule_temp = NULL;
    struct list_head *pos, *temp;
    int current_cnt = 0;
    
    list_for_each_safe(pos, temp, &UF_rulelist) 
    {
        prule_temp = list_entry(pos, UF_kruleinfo_S, list);
        //printk(KERN_EMERG"rule=%s\n", prule_temp->rule.name); 
        memcpy(&(prule[current_cnt]), &(prule_temp->rule), sizeof(UF_RuleInfo_S));
        current_cnt++;
        if (current_cnt >= max_getcnt)
        {
            break;
        }
	}

    phead->count = current_cnt;
    phead->defaultact = default_act;
    phead->https  = url_advanced;
    //printk(KERN_EMERG"count=%u\n", current_cnt); 
    return 0;
};

int UF_entry_addurl(unsigned int optcode,
                    void *input, unsigned int inputlen, 
                    void *output, unsigned int outputlen)
{
    int ret = 0;
    UF_RuleInfo_S *pruleinfo = NULL;
    UF_CfgOutput_S *presult = NULL;
    
    if ((NULL == input) || (NULL == output) ||
        (sizeof(UF_RuleInfo_S) != inputlen) || 
        (sizeof(UF_CfgOutput_S) != outputlen))
    {
        printk(KERN_EMERG"kernel_addrule: invalid param!\n");
        return -1;
    }
    
    pruleinfo = (UF_RuleInfo_S *)input;
    presult = (UF_CfgOutput_S *)output;
    write_lock_irq(&urlrule_lock);
    ret = UF_proc_addurl(pruleinfo, output);
    write_unlock_irq(&urlrule_lock);
    
    return ret;
}

int UF_entry_delurl(unsigned int optcode,
                    void *input, unsigned int inputlen, 
                    void *output, unsigned int outputlen)
{
    int ret = 0;
    UF_RuleInfo_S *pruleinfo = NULL;
    UF_CfgOutput_S *presult = NULL;
    
    if ((NULL == input) || (NULL == output) ||
        (sizeof(UF_RuleInfo_S) != inputlen) || 
        (sizeof(UF_CfgOutput_S) != outputlen))
    {
        printk(KERN_EMERG"UF_entry_delurl: invalid param!\n");
        return -1;
    }
    
    pruleinfo = (UF_RuleInfo_S *)input;
    presult = (UF_CfgOutput_S *)output;
    write_lock_irq(&urlrule_lock);
    ret = UF_proc_delurl(pruleinfo, output);
    write_unlock_irq(&urlrule_lock);
    
    return ret;
}

int UF_entry_show(unsigned int optcode,
                  void *input, unsigned int inputlen, 
                  void *output, unsigned int outputlen)
{
    int ret = 0;
    UF_ShowHead_S *phead = NULL;
    UF_RuleInfo_S *prule = NULL;
    int max_getcnt = 0;
    int min_outlen = sizeof(UF_ShowHead_S) + sizeof(UF_RuleInfo_S);
    
    if ((NULL == output) || (outputlen < min_outlen))
    {
        printk(KERN_EMERG"UF_entry_show: invalid param!\n");
        return -1;
    }
    
    phead = (UF_ShowHead_S *)output;
    prule = (UF_RuleInfo_S *)(output+sizeof(UF_ShowHead_S));
    max_getcnt = (outputlen-sizeof(UF_ShowHead_S))/sizeof(UF_RuleInfo_S);
    read_lock_irq(&urlrule_lock);
    ret = UF_proc_show(phead, prule, max_getcnt);
    read_unlock_irq(&urlrule_lock);
    
    return ret;
}

int UF_entry_action(unsigned int optcode,
                    void *input, unsigned int inputlen, 
                    void *output, unsigned int outputlen)
{
    UF_RuleInfo_S *pruleinfo = NULL;
    
    if ((NULL == input) || (NULL == output) ||
        (sizeof(UF_RuleInfo_S) != inputlen) || 
        (sizeof(UF_CfgOutput_S) != outputlen))
    {
        printk(KERN_EMERG"UF_entry_action: invalid param!\n");
        return -1;
    }
    
    pruleinfo = (UF_RuleInfo_S *)input;
    default_act = pruleinfo->action;
    
    return 0;
}

int UF_entry_https(unsigned int optcode,
                   void *input, unsigned int inputlen, 
                   void *output, unsigned int outputlen)
{
    UF_RuleInfo_S *pruleinfo = NULL;
    
    if ((NULL == input) || (NULL == output) ||
        (sizeof(UF_RuleInfo_S) != inputlen) || 
        (sizeof(UF_CfgOutput_S) != outputlen))
    {
        printk(KERN_EMERG"UF_entry_https: invalid param!\n");
        return -1;
    }
    
    pruleinfo = (UF_RuleInfo_S *)input;
    url_advanced = pruleinfo->https;
    
    return 0;
}

int UF_entry_debug(unsigned int optcode,
                   void *input, unsigned int inputlen, 
                   void *output, unsigned int outputlen)
{    
    if ((NULL == input) || (NULL == output) ||
        (sizeof(UF_RuleInfo_S) != inputlen) || 
        (sizeof(UF_CfgOutput_S) != outputlen))
    {
        printk(KERN_EMERG"UF_entry_debug: invalid param!\n");
        return -1;
    }
    
    match_debugging = 1;
    default_debugging = 1;
    match_count = 0;
    default_count = 0;
    
    return 0;
}

int UF_is_httpreq(void *packet)
{
    struct ts_state state;
    struct sk_buff *skb = (struct sk_buff *)packet;
    
    if ((NULL != match_get) && (!IS_ERR(match_get)) &&
        (UINT_MAX != skb_find_text(skb, 0, skb->len, match_get, &state)))
    {
        return 1;
    }

    if ((NULL != match_post) && (!IS_ERR(match_post)) &&
        (UINT_MAX != skb_find_text(skb, 0, skb->len, match_post, &state)))
    {
        return 1;
    }

    return 0;
}

unsigned int UF_match_rule(UF4_MatchInfo_S *pfilter, void *packet)
{
    unsigned int ret = NF_ACCEPT;
    UF_kruleinfo_S *prule = NULL;
    struct list_head *pos, *temp;
    struct ts_state state;
    int match_ret = 0;
    struct sk_buff *skb = (struct sk_buff *)packet;

    read_lock_irq(&urlrule_lock);
    if (UF_is_httpreq(skb))
    {
        list_for_each_safe(pos, temp, &UF_rulelist) 
        {
    		prule = list_entry(pos, UF_kruleinfo_S, list);

            /* url */
            if ((NULL != prule->match_url) && (!IS_ERR(prule->match_url)) &&
                (UINT_MAX != skb_find_text(skb, 0, skb->len, prule->match_url, &state)))
            {
                match_ret = 1;
                ret = prule->rule.action;
                if (match_debugging)
                {
                    printk("<0>""match: key=%s, action=%s\n", 
                           prule->rule.url, prule->rule.action ? "pass" : "drop");
                    match_count++;
                    if (match_count > 10)
                    {
                        match_debugging = 0;
                        match_count = 0;
                    }
                }
                break;
            }
    	}
        if (!match_ret)
        {
            ret = default_act;
            if (match_debugging)
            {
                printk("<0>""match: default, action=%s\n", default_act ? "pass" : "drop");
                default_count++;
                if (default_count > 10)
                {
                    default_debugging = 0;
                    default_count = 0;
                }
            }
        }
    }
    read_unlock_irq(&urlrule_lock);

    return ret;
}

void UF_rule_init(void)
{
    rwlock_init(&urlrule_lock);
    INIT_LIST_HEAD(&UF_rulelist);
    
    write_lock_irq(&urlrule_lock);
    UF_build_get();
    write_unlock_irq(&urlrule_lock);
}

void UF_rule_exit(void)
{
    UF_kruleinfo_S *prule = NULL;
    struct list_head *pos, *temp;
    write_lock_irq(&urlrule_lock);
    list_for_each_safe(pos, temp, &UF_rulelist) {
		prule = list_entry(pos, UF_kruleinfo_S, list);
        if ((NULL != prule->match_url) && (!IS_ERR(prule->match_url)))
        {
            textsearch_destroy(prule->match_url);
            prule->match_url = NULL;
        }
    
		list_del(pos);
		kfree(prule);
	}
    UF_release_get();
    write_unlock_irq(&urlrule_lock);
}
